package com.aurora.aspect;

import com.aurora.config.datasource.DynamicDataSourceHolder;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * aop实现service的方法的切面处理
 */
@Aspect
@Order(-10) // 保证该AOP在@Transactional之前执行
@Component
@Log4j2
public class DynamicDataSourceAspect implements InitializingBean {

    private List<String> slaveMethodPattern = new ArrayList<>();

    private static final String[] defaultSlaveMethodStart = new String[]{"query", "find", "get", "page", "list"};

    @Resource
    TransactionInterceptor txAdvice;

    @Pointcut("execution(* com.aurora.service..*.*(..))")
    public void targetDataSource() {
    }

    @Before(value = "targetDataSource()")
    public void restoreDataSource(JoinPoint point) {
        // 获取到当前执行的方法名
        String methodName = point.getSignature().getName();
        boolean isSlave = false;
        if (slaveMethodPattern.isEmpty()) {
            // 当前Spring容器中没有配置事务策略，采用方法名匹配方式
            isSlave = isSlave(methodName);
        } else {
            // 使用策略规则匹配
            for (String mappedName : slaveMethodPattern) {
                if (isMatch(methodName, mappedName)) {
                    isSlave = true;
                    break;
                }
            }
        }
        DynamicDataSourceHolder.markMaster();
//        if (isSlave) {
//            // 标记为读库
//            DynamicDataSourceHolder.markSlave();
//        } else {
//            // 标记为写库
//            DynamicDataSourceHolder.markMaster();
//        }
    }

    protected boolean isMatch(String methodName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, methodName);
    }

    @After(value = "targetDataSource()")
    public void clearDataSource() {
        DynamicDataSourceHolder.clearDataSourceType();
    }

    /**
     * 判断是否为读库
     *
     * @param methodName
     * @return
     */
    private Boolean isSlave(String methodName) {
        // 方法名以query、find、get开头的方法名走从库
        return StringUtils.startsWithAny(methodName, defaultSlaveMethodStart);
    }

    /**
     * 读取事务管理中的策略
     *
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {
        if (txAdvice == null) {
            // 没有配置事务管理策略
            return;
        }
        //从txAdvice获取到策略配置信息
        TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();
        if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {
            return;
        }
        //使用反射技术获取到NameMatchTransactionAttributeSource对象中的nameMap属性值
        NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;
        Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
        nameMapField.setAccessible(true); //设置该字段可访问
        //获取nameMap的值
        Map<String, TransactionAttribute> map = (Map<String, TransactionAttribute>) nameMapField.get(matchTransactionAttributeSource);

        //遍历nameMap
        for (Map.Entry<String, TransactionAttribute> entry : map.entrySet()) {
            if (!entry.getValue().isReadOnly()) {//判断之后定义了ReadOnly的策略才加入到slaveMethodPattern
                continue;
            }
            slaveMethodPattern.add(entry.getKey());
        }
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        setTxAdvice(txAdvice);

    }
}